use regex::Regex;
use std::{collections::HashMap, process::Command, str, time::SystemTime};

struct DouyuInfo {
    did: String,
    rid: String,
    resp_text: String,
    t10: String,
    t13: String,
    client: reqwest::Client,
}

impl DouyuInfo {
    async fn get_real_room_id(&mut self, show_room_id: &str) {
        let url = format!("https://m.douyu.com/{}", show_room_id);
        let resp = self.client.get(url).send().await.expect("Request Failed");
        self.resp_text = resp
            .text_with_charset("utf-8")
            .await
            .expect("Fail Read texts");

        let re = Regex::new("rid\":(\\d{1,7}),\"vipId").unwrap();
        let matcheds = re
            .captures(&self.resp_text)
            .unwrap()
            .get(1)
            .expect("failed");
        self.rid = matcheds.as_str().to_string();
    }

    async fn get_pre_url(&mut self) {
        let url = format!(
            "https://playweb.douyucdn.cn/lapi/live/hlsH5Preview/{}",
            &self.rid
        );

        let auth_digest = md5::compute(format!("{}{}", &self.rid, &self.t13));
        let auth = format!("{:x}", auth_digest);

        let mut body = HashMap::new();
        body.insert("rid", &self.rid);
        body.insert("did", &self.did);

        let resp = self
            .client
            .post(url)
            .header("rid", &self.rid)
            .header("time", &self.t13)
            .header("auth", &auth)
            .form(&body)
            .send()
            .await
            .expect("get pre url post error");

        let result: serde_json::Value = resp.json().await.expect("parse json error");
        println!(
            "h5_pre_url: [{}/{}]",
            result["data"]["rtmp_url"].as_str().unwrap_or("failed"),
            result["data"]["rtmp_live"].as_str().unwrap_or("failed")
        );
    }

    async fn get_url_from_pc(&mut self) {
        let html_texts = self
            .client
            .get(format!("https://www.douyu.com/{}", self.rid))
            .send()
            .await
            .expect("get pc req failed")
            .text_with_charset("utf-8")
            .await
            .unwrap();

        let re =
            Regex::new(r#"(vdwdae325w_64we[\s\S]*function ub98484234[\s\S]*?)function"#).unwrap();
        let matchs = re.captures(&html_texts).unwrap();
        let func_ub98484234_str = matchs.get(1).unwrap().as_str();
        let sub_re = Regex::new(r#"eval.*;}"#).unwrap();
        let func_ub98484234 = sub_re.replace(func_ub98484234_str, "strc;}");
        let func_ub_str: &str = &func_ub98484234;

        let func_ub98484234_result = Command::new("node")
            .arg("-e")
            .arg(format!("{};console.log(ub98484234());", func_ub_str))
            .output()
            .expect("run func_ub98484234 failed");

        let func_out_str = str::from_utf8(&func_ub98484234_result.stdout).unwrap();
        let func_out_re = Regex::new(r#"v=(\d+)"#).unwrap();
        let v = func_out_re
            .captures(func_out_str)
            .unwrap()
            .get(1)
            .unwrap()
            .as_str();
        let rb_digest = md5::compute(format!("{}{}{}{}", self.rid, self.did, self.t10, v));
        let rb = format!("{:x}", rb_digest);

        let sub_func_sign_re = Regex::new(r#"return rt;}\);?"#).unwrap();
        let func_sign_str_raw: &str = &sub_func_sign_re.replace(func_out_str, "return rt;}");
        let func_sign = func_sign_str_raw
            .replace("(function (", "function sign(")
            .replace("CryptoJS.MD5(cb).toString()", format!("{:?}", &rb).as_str());
        // exec sign function
        let func_sign_result = Command::new("node")
            .arg("-e")
            .arg(format!(
                "{};console.log(sign({:?},{:?},{:?}));",
                func_sign, self.rid, self.did, self.t10
            ))
            .output()
            .expect("Error run sign function");
        let func_sign_out_str = str::from_utf8(&func_sign_result.stdout).unwrap().trim_end();
        // println!("func_sign_out_str: {}", func_sign_out_str);
        let params = format!("{}&cdn={}&rate={}", func_sign_out_str, "ws-h5", "0");
        // println!("params: {}", params);
        let h5play_resp_value: serde_json::Value = self
            .client
            .post(format!(
                "https://www.douyu.com/lapi/live/getH5Play/{}?{}",
                self.rid, params
            ))
            .body(params)
            .send()
            .await
            .expect("send last api failed")
            .json()
            .await
            .unwrap();
        let final_data = &h5play_resp_value["data"];
        println!(
            "pc_real_url: [{}/{}]",
            final_data["rtmp_url"].as_str().unwrap_or("failed"),
            final_data["rtmp_live"].as_str().unwrap_or("failed")
        )
    }
}

pub async fn get_douyu_real_url(room_id: &str) {
    let timestamp = SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .expect("timestamp error");

    let mut default_header = reqwest::header::HeaderMap::new();
    default_header.insert(
        "User-Agent",
        reqwest::header::HeaderValue::from_static(concat!(
            "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) ",
            "AppleWebKit/537.36 (KHTML, like Gecko) ",
            "Chrome/75.0.3770.100 Mobile Safari/537.36"
        )),
    );

    let client = reqwest::Client::builder()
        .default_headers(default_header)
        .build()
        .expect("Error");

    let mut info = DouyuInfo {
        did: String::from("10000000000000000000000000001501"),
        rid: String::from(""),
        resp_text: String::from(""),
        t10: timestamp.as_secs().to_string(),
        t13: format!("{}000", timestamp.as_secs()),
        client: client,
    };

    info.get_real_room_id(&room_id).await;
    info.get_pre_url().await;
    info.get_url_from_pc().await;
}
